﻿using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Messaging;
using ICSharpCode.AvalonEdit;
using JiveMessenger.AvalonEdit;
using JiveMessenger.CustomControls;
using JiveMessenger.ImagePreview.Cache;
using JiveMessenger.ImagePreview.GfyCat;
using JiveMessenger.ImagePreview.Imgur;
using JiveMessenger.ImagePreview.Wikimedia;
using JiveMessenger.Utilities;
using JiveMessenger.Views;
using JmShared;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Caching;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace JiveMessenger.ImagePreview
{
    internal enum ImageFormat
    {
        bmp,
        jpeg,
        gif,
        tiff,
        png,
        unknown
    }
    public static class ImagePreviewManager
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private static string _lastHoveredLink;
        private static Point ImagePreviewMousePosition;
        private static DispatcherTimer _hoverTimer;
        private static ConcurrentDictionary<string, bool> _ignoreDownloadFinished;
        private static Thread _hoverZoomThread;
        private static bool _isDownloadingImage;
        private static bool _isDownloadingImgurInfo;
        private static TimeoutWebClient _wc;
        private static TimeoutWebClient _imgurInfo;
        private static ConcurrentDictionary<string, CachedPreviewImage> _albumImageQueue;
        private static int _albumImageIndex = 0;
        private static Thread _imageQueueBackgroundDownloadThread;
        private static int _imgDownloadCount = 0;
        private static bool _stopImagePopup;
        private static string _linkUrl;

        private static ImagePreviewWindow ImagePreview { get; set; }
        private static DownloadProgressWindow DownloadProgress { get; set; }

        #region Attached Properties
        [AttachedPropertyBrowsableForType(typeof(TextEditor))]
        public static GfyCatVideoFormat GetGfyCatVideoFormat(DependencyObject obj)
        {
            return (GfyCatVideoFormat)obj.GetValue(GfyCatVideoFormatProperty);
        }

        public static void SetGfyCatVideoFormat(DependencyObject obj, GfyCatVideoFormat value)
        {
            obj.SetValue(GfyCatVideoFormatProperty, value);
        }

        // Using a DependencyProperty as the backing store for GfyCatVideoFormat.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GfyCatVideoFormatProperty =
            DependencyProperty.RegisterAttached("GfyCatVideoFormat", typeof(GfyCatVideoFormat), typeof(ImagePreviewManager), new PropertyMetadata(GfyCatVideoFormat.mp4, GfyCatVideoFormatChanged));

        [AttachedPropertyBrowsableForType(typeof(TextEditor))]
        public static bool GetEnableGfyCat(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnableGfyCatProperty);
        }

        public static void SetEnableGfyCat(DependencyObject obj, bool value)
        {
            obj.SetValue(EnableGfyCatProperty, value);
        }


        // Using a DependencyProperty as the backing store for EnableGfyCat.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableGfyCatProperty =
            DependencyProperty.RegisterAttached("EnableGfyCat", typeof(bool), typeof(ImagePreviewManager), new PropertyMetadata(false, EnableGfyCatChanged));


        [AttachedPropertyBrowsableForType(typeof(TextEditor))]
        public static bool GetEnableImagePreview(TextEditor obj)
        {
            return (bool)obj.GetValue(EnableImagePreviewBehaviorProperty);
        }

        public static void SetEnableImagePreview(TextEditor obj, bool value)
        {
            obj.SetValue(EnableImagePreviewBehaviorProperty, value);
        }

        // Using a DependencyProperty as the backing store for EnableImagePreviewBehavior.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableImagePreviewBehaviorProperty =
            DependencyProperty.RegisterAttached(
            "EnableImagePreview",
            typeof(bool),
            typeof(ImagePreviewManager),
            new PropertyMetadata(false,
                EnableImagePreviewBehaviorChanged));


        public static bool GetEnableImgurVideo(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnableImgurVideoProperty);
        }

        public static void SetEnableImgurVideo(DependencyObject obj, bool value)
        {
            obj.SetValue(EnableImgurVideoProperty, value);
        }

        // Using a DependencyProperty as the backing store for EnableImgurVideo.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableImgurVideoProperty =
            DependencyProperty.RegisterAttached("EnableImgurVideo", typeof(bool), typeof(ImagePreviewManager), new PropertyMetadata(false, EnableImgurVideoChanged));



        public static ImgurVideoFormat GetImgurVideoFormat(DependencyObject obj)
        {
            return (ImgurVideoFormat)obj.GetValue(ImgurVideoFormatProperty);
        }

        public static void SetImgurVideoFormat(DependencyObject obj, ImgurVideoFormat value)
        {
            obj.SetValue(ImgurVideoFormatProperty, value);
        }

        // Using a DependencyProperty as the backing store for ImgurVideoFormat.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ImgurVideoFormatProperty =
            DependencyProperty.RegisterAttached("ImgurVideoFormat", typeof(ImgurVideoFormat), typeof(ImagePreviewManager), new PropertyMetadata(ImgurVideoFormat.WebM, ImgurVideoFormatChanged));
        #endregion

        private static void ImgurVideoFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ImgurManager.ImgurVideoFormat = (ImgurVideoFormat)e.NewValue;
        }


        private static void EnableImgurVideoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ImgurManager.EnableImgurVideo = (bool)e.NewValue;
        }

        private static void GfyCatVideoFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            GfyCatManager.GfyCatVideoFormat = (GfyCatVideoFormat)e.NewValue;
        }
        private static void EnableGfyCatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            GfyCatManager.EnableGfyCat = (bool)e.NewValue;
        }
        private static void EnableImagePreviewBehaviorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextEditor editor = d as TextEditor;
            if (editor == null)
                return;

            if ((bool)e.OldValue == true)
            {
                editor.Unloaded -= TextEditor_Unloaded;
                StopImagePreview(editor);
                //remove bindings etc and disable imagepreview.
            }
            if ((bool)e.NewValue == true)
            {
                //add bindings and enable imagepreview
                editor.Unloaded += TextEditor_Unloaded;
                editor.DoWhenLoaded(StartImagePreview);
            }
        }

        static void TextEditor_Unloaded(object sender, RoutedEventArgs e)
        {
            TextEditor editor = sender as TextEditor;
            if (editor == null)
                return;
            editor.Unloaded -= TextEditor_Unloaded;
            StopImagePreview(editor);
            //clean up stuff
        }

        private static void StartImagePreview(TextEditor te)
        {
            _ignoreDownloadFinished = new ConcurrentDictionary<string, bool>();
            _albumImageQueue = new ConcurrentDictionary<string, CachedPreviewImage>();

            te.MouseHover += TextEditor_MouseHover;
            te.MouseMove += TextEditor_MouseMove;
            te.PreviewMouseWheel += TextEditor_PreviewMouseWheel;
            StartTimer(te);
        }
        private static void StopImagePreview(TextEditor te)
        {
            te.MouseHover -= TextEditor_MouseHover;
            te.MouseMove -= TextEditor_MouseMove;
            te.PreviewMouseWheel -= TextEditor_PreviewMouseWheel;
            StopTimer();
        }

        private static void StartTimer(TextEditor cc)
        {
            if (_hoverTimer != null)
                StopTimer();
            _hoverTimer = new DispatcherTimer();
            _hoverTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);
            _hoverTimer.Tick += _hoverTimer_Tick;
            _hoverTimer.Tag = cc;
            _hoverTimer.Start();
        }
        private static void StopTimer()
        {
            if (_hoverTimer != null)
            {
                _hoverTimer.Tick -= _hoverTimer_Tick;
                _hoverTimer.Tag = null;
                _hoverTimer.Stop();
                _hoverTimer = null;
            }
        }

        private static void ScrollAlbum(int delta)
        {
            if (ImagePreview == null)
                return;
            int newIndex = 1;
            if (delta > 0)//Scrolled up
                newIndex = _albumImageIndex - 1;
            else//Scrolled down
                newIndex = _albumImageIndex + 1;

            if (newIndex < _albumImageQueue.Count && newIndex >= 0)
            {
                List<CachedPreviewImage> images = _albumImageQueue.Values.Where(x => x != null).ToList();
                images.Sort((x, y) =>
                {
                    return (x.ImgNum.CompareTo(y.ImgNum));
                });
                bool canScrollToNext = false;
                if (delta > 0 && images.Count > newIndex && images[newIndex].ImgNum == newIndex + 1)
                    canScrollToNext = true;
                else if (delta < 0 && images.Count > newIndex && images[newIndex].ImgNum == newIndex + 1)
                    canScrollToNext = true;

                if (canScrollToNext)
                {
                    CachedPreviewImage img = images[newIndex];
                    ImagePreview.ImageText = img.Title;
                    ImagePreview.ImgCount = img.TotalImgCount;
                    ImagePreview.ImgNumber = img.ImgNum;
                    _albumImageIndex = newIndex;

                    if (!string.IsNullOrEmpty(img.VideoUrl))//IsValidGifUrl(img.Url) || IsValidVideoUrl(img.Url))
                    {
                        ImagePreview.VideoUrl = img.VideoUrl;
                        ImagePreview.ImgSource = null;
                    }
                    else if (IsValidImageUrl(img.Url) && img.Source != null)
                    {
                        App.Current.Dispatcher.Invoke(() =>
                        {
                            ImagePreview.ImgSource = CreateImageSource(img.Source);
                            ImagePreview.VideoUrl = "";
                        });
                    }

                    ImgurInfo info = ImgurManager.GetImgurInfoForUrl(_linkUrl);
                    StartImageQueueBackgroundDownload(info, _albumImageIndex);
                }
            }
        }


        private static void ShowImage(Uri link)
        {

            if (_hoverZoomThread != null && _hoverZoomThread.IsAlive)
            {
                Logger.Debug("Image preview thread is alive, returning");
                return;
            }

            if (_isDownloadingImage)
            {
                Logger.Debug("Preview Image is downloading, returning");
                return;
            }
            Uri navigateUri = link;

            string imgUrl = link.AbsoluteUri;
            bool isImage = IsValidImageUrl(navigateUri.AbsoluteUri);
            bool isGif = IsValidGifUrl(navigateUri.AbsoluteUri);
            bool isVideo = !isImage && IsValidVideoUrl(navigateUri.AbsoluteUri);
            ImgurLink imgurLink = !isVideo ? ImgurManager.GetImgurId(navigateUri.AbsoluteUri) : null;
            GfyLink gfyLink = GfyCatManager.EnableGfyCat ? GfyCatManager.GetGfyCatLink(navigateUri.AbsoluteUri) : null;
            WikimediaLink wikiLink = isImage && imgurLink == null ? WikimediaManager.GetWikiMediaLink(navigateUri) : null;

            if (!isImage && !isGif && !isVideo && gfyLink == null && imgurLink == null)
            {
                Logger.Debug("{0} is not a valid file for preview", imgUrl);
                return;
            }

            _hoverZoomThread = new Thread(() =>
            {
                ObjectCache cache = MemoryCache.Default;
                bool showImage = true;

                if (imgurLink != null)
                {
                    if (imgurLink.LinkType == ImgurLinkType.Album || imgurLink.LinkType == ImgurLinkType.Gallery)
                        imgUrl = navigateUri.AbsoluteUri;
                    else if (isGif)
                        imgUrl = ImgurManager.GetImgurLink(imgurLink.ID, false, ".gif");
                    else
                        imgUrl = ImgurManager.GetImgurLink(imgurLink.ID, false);

                }
                else if (wikiLink != null)
                {
                    imgUrl = wikiLink.ThumbnailUrl;
                }
                else
                {
                    imgUrl = navigateUri.AbsoluteUri;
                }

                if (_linkUrl != imgUrl)
                    ClearPreviewData();

                CachedPreviewImage cachedImage = cache[imgUrl] as CachedPreviewImage;
                if (cachedImage != null && (cachedImage.Source != null || !String.IsNullOrEmpty(cachedImage.VideoUrl)))
                {
                    if (ImagePreview.ImgSource == null)
                    {
                        App.Current.Dispatcher.Invoke(() =>
                        {
                            if (!String.IsNullOrEmpty(cachedImage.Title))
                                ImagePreview.ImageText = cachedImage.Title;

                            if (cachedImage.Source != null)
                                ImagePreview.ImgSource = CreateImageSource(cachedImage.Source);

                            if (!string.IsNullOrEmpty(cachedImage.VideoUrl))
                            {
                                ImagePreview.VideoUrl = cachedImage.VideoUrl;

                                ImagePreview.VideoSize = cachedImage.Size;
                                isVideo = true;
                            }
                            _linkUrl = imgUrl;
                            showImage = true;
                        });
                    }
                }
                else if (!cache.Any(x=>x.Key.Contains(imgUrl)))
                {
                    ClearPreviewData();
                }
                Messenger.Default.Send(new NotificationMessage<int>(null, 0, "SetDownloadErrorCode"));
                if (isGif && gfyLink == null && String.IsNullOrEmpty(ImagePreview.VideoUrl))
                {
                    if (GfyCatManager.EnableGfyCat || (ImgurManager.EnableImgurVideo && imgurLink != null))
                        showImage = LoadGif(imgUrl, imgurLink);
                    else
                        showImage = LoadImage(imgUrl, true, imgurLink);
                }
                else if (isVideo && String.IsNullOrEmpty(ImagePreview.VideoUrl))
                {
                    showImage = LoadVideo(imgUrl);
                }
                else if (gfyLink != null && String.IsNullOrEmpty(ImagePreview.VideoUrl))
                {
                    GfyItem gfy = null;
                    try
                    {
                        gfy = GfyCatManager.GetGfyItemForName(gfyLink.ID);
                    }
                    catch (WebException ex)
                    {
                        Logger.ErrorException("Error getting GfyCat item", ex);
                    }
                    if (gfy != null)
                    {
                        showImage = LoadVideo(gfy, imgUrl);
                    }
                }
                else if (!isGif && !isVideo && ImagePreview.ImgSource == null)
                {
                    showImage = LoadImage(imgUrl, false, imgurLink);
                }

                if (showImage)
                {
                    if (_wc != null)
                    {
                        _wc.CancelAsync();
                        _wc = null;

                        Logger.Debug("Canceling image preview async web request");
                    }
                    App.Current.Dispatcher.BeginInvoke(() =>
                    {
                        if (!string.IsNullOrEmpty(ImagePreview.VideoUrl))
                            ImagePreview.UpdatePosition(ImagePreview.VideoSize);
                        else
                            if (ImagePreview.ImgSource != null)
                                ImagePreview.UpdatePosition();


                        ImagePreview.IsWindowVisible = true;
                        Logger.Debug("Setting image preview to visible");
                    });


                }
            })
            {
                IsBackground = true,
                Name = "ImageZoomThread"
            };

            _hoverZoomThread.Start();
        }


        private static void ClearPreviewData()
        {
            _albumImageIndex = 0;
            ImagePreview.ImgCount = 0;
            ImagePreview.ImgNumber = 0;
            ImagePreview.ImageText = string.Empty;
            ImagePreview.ImgSource = null;
            ImagePreview.VideoUrl = null;
            ImagePreview.VideoSize = new Size(0, 0);
            ImagePreview.IsWindowVisible = false;
            _albumImageQueue.Clear();
            Logger.Debug("Cleared image preview data");


        }

        private static bool LoadVideo(string videoUrl)
        {
            bool showVideo = false;
            Messenger.Default.Send(new NotificationMessage<bool>(null, true, "ShowDownloadProgressWindow"));
            _linkUrl = videoUrl;
            string imageUrl = videoUrl;
            string downloadUrl = videoUrl;
            ObjectCache cache = MemoryCache.Default;

            string imgTitle = "";

            if (!string.IsNullOrEmpty(imgTitle))
                ImagePreview.ImageText = imgTitle;

            int imgCount = 1;
            ImagePreview.VideoUrl = videoUrl;


            ImagePreview.ImgCount = imgCount;
            ImagePreview.ImgNumber = 1;
            CachedPreviewImage cachedImage = cache.Get(videoUrl) as CachedPreviewImage;
            if (cachedImage == null)
            {
                cachedImage = new CachedPreviewImage(null, new Size(0, 0), imgTitle, videoUrl, videoUrl, 1, imgCount);
                var policy = new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) };
                cache.Set(videoUrl, cachedImage, policy);
            }
            else
            {
                cachedImage.ImgNum = 1;

                if (!String.IsNullOrEmpty(imgTitle))
                    cachedImage.Title = imgTitle;
                cachedImage.TotalImgCount = imgCount;
                cachedImage.VideoUrl = videoUrl;
            }
            showVideo = true;

            return showVideo;
        }
        private static bool LoadVideo(GfyItem item, string imgUrl)
        {
            bool showVideo = false;
            Messenger.Default.Send(new NotificationMessage<bool>(null, true, "ShowDownloadProgressWindow"));
            _linkUrl = imgUrl;
            string imageUrl = imgUrl;
            string downloadUrl = imgUrl;
            ObjectCache cache = MemoryCache.Default;
            if (item != null && !String.IsNullOrEmpty(GfyCatManager.GetVideoUrlForGfyItem(item)))
            {
                string imgTitle = "";

                imgTitle = item.title;

                int imgCount = 1;
                App.Current.Dispatcher.Invoke(() =>
                    {
                        if (!string.IsNullOrEmpty(imgTitle))
                            ImagePreview.ImageText = imgTitle;

                        ImagePreview.VideoUrl = GfyCatManager.GetVideoUrlForGfyItem(item);
                        ImagePreview.VideoSize = new Size(item.width, item.height);
                        ImagePreview.ImgCount = imgCount;
                        ImagePreview.ImgNumber = 1;
                    });
                CachedPreviewImage cachedImage = cache.Get(imgUrl) as CachedPreviewImage;
                if (cachedImage == null)
                {
                    cachedImage = new CachedPreviewImage(null, new Size(item.width, item.height), imgTitle, imgUrl, GfyCatManager.GetVideoUrlForGfyItem(item), 1, imgCount);
                    var policy = new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) };
                    cache.Set(imgUrl, cachedImage, policy);
                }
                else
                {
                    cachedImage.ImgNum = 1;
                    cachedImage.Size = new Size(item.width, item.height);
                    if (!String.IsNullOrEmpty(imgTitle))
                        cachedImage.Title = imgTitle;
                    cachedImage.TotalImgCount = imgCount;
                    cachedImage.VideoUrl = GfyCatManager.GetVideoUrlForGfyItem(item);
                }
                showVideo = true;
            }
            return showVideo;
        }

        private static bool LoadGif(string imgUrl, ImgurLink imgurLink = null)
        {
            ObjectCache cache = MemoryCache.Default;
            bool showImage = true;
            bool loadAsImgurVideo = false;
            Messenger.Default.Send(new NotificationMessage<bool>(null, true, "ShowDownloadProgressWindow"));
            _linkUrl = imgUrl;
            string imageUrl = imgUrl;
            string downloadUrl = imgUrl;
            ImgurInfo info = null;
            if (imgurLink != null)
            {
                ProcessedImgurLink imgurResponse = ProcessImgurLink(imgurLink, imgUrl);
                info = imgurResponse.Info;
                imageUrl = imgurResponse.Url;
                downloadUrl = imgurResponse.ThumbnailUrl;


                if (imgurResponse.CachedImage == null)
                {
                    string title = "";
                    int imgCount = 1;
                    string videourl = "";

                    if (info.data.images == null || info.data.images.Count == 0)
                    {
                        title = ImgurManager.GetImageTitle(info.data);
                        videourl = ImgurManager.GetVideoUrlForImgurItem(info.data);
                    }
                    else
                    {
                        int imgIndex = 0;
                        if (imgurResponse.CachedImage != null && imgurResponse.CachedImage.ImgNum > 0)
                            imgIndex = imgurResponse.CachedImage.ImgNum - 1;
                        ImgurImage img = info.data.images[imgIndex];
                        imageUrl = img.link;
                        imgCount = info.data.images_count;
                        videourl = ImgurManager.GetVideoUrlForImgurItem(img);
                        title = ImgurManager.GetImageTitle(img);
                    }
                    var cachedImage = new CachedPreviewImage(null, new Size(info.data.width, info.data.height), title, imageUrl, videourl, 1, imgCount);
                    var policy = new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) };
                    cache.Set(imageUrl, cachedImage, policy);
                    _albumImageQueue[imageUrl] = cachedImage;
                    imgurResponse.CachedImage = cachedImage;
                }
                if (!String.IsNullOrEmpty(imgurResponse.CachedImage.VideoUrl))
                {
                    App.Current.Dispatcher.Invoke(() =>
                        {
                            ImagePreview.ImageText = imgurResponse.CachedImage.Title;
                            ImagePreview.VideoUrl = imgurResponse.CachedImage.VideoUrl;
                            ImagePreview.VideoSize = imgurResponse.CachedImage.Size;
                            ImagePreview.ImgNumber = _albumImageIndex + 1;
                            ImagePreview.ImgCount = imgurResponse.CachedImage.TotalImgCount;
                        });

                    loadAsImgurVideo = true;
                }


            }
            if (!loadAsImgurVideo)
            {
                if (String.IsNullOrEmpty(ImagePreview.VideoUrl))
                {
                    GfyItem item = null;
                    try
                    {
                        item = GfyCatManager.GetGfyItem(imageUrl);
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorException("Error getting GfyItem", ex);
                    }

                    if (item != null && !String.IsNullOrEmpty(GfyCatManager.GetVideoUrlForGfyItem(item)))
                    {
                        string imgTitle = "";
                        imgTitle = item.title;

                        int imgCount = 1;
                        App.Current.Dispatcher.Invoke(() =>
                            {
                                if (!string.IsNullOrEmpty(imgTitle))
                                    ImagePreview.ImageText = imgTitle;

                                ImagePreview.VideoUrl = GfyCatManager.GetVideoUrlForGfyItem(item);
                                ImagePreview.VideoSize = new Size(item.width, item.height);
                                ImagePreview.ImgCount = imgCount;
                                ImagePreview.ImgNumber = 1;
                            });
                        CachedPreviewImage cachedImage = cache.Get(imageUrl) as CachedPreviewImage;
                        if (cachedImage == null)
                        {
                            cachedImage = new CachedPreviewImage(null, new Size(item.width, item.height), imgTitle, imageUrl, GfyCatManager.GetVideoUrlForGfyItem(item), 1, imgCount);
                            var policy = new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) };
                            cache.Set(imageUrl, cachedImage, policy);
                        }
                        else
                        {
                            cachedImage.ImgNum = 1;
                            cachedImage.Size = new Size(item.width, item.height);
                            if (!String.IsNullOrEmpty(imgTitle))
                                cachedImage.Title = imgTitle;
                            cachedImage.TotalImgCount = imgCount;
                            cachedImage.VideoUrl = GfyCatManager.GetVideoUrlForGfyItem(item);
                        }

                    }
                    else
                    {
                        return LoadImage(imgUrl, true, imgurLink);
                    }
                }
            }
            return showImage;
        }

        private static bool LoadImage(string imgUrl, bool forceLoadGifAsImage, ImgurLink imgurLink = null)
        {
            bool showImage = true;
            Messenger.Default.Send(new NotificationMessage<bool>(null, true, "ShowDownloadProgressWindow"));
            _linkUrl = imgUrl;
            string imageUrl = imgUrl;
            string downloadUrl = imgUrl;

            ImgurInfo info = null;
            if (imgurLink != null)
            {
                ProcessedImgurLink imgurResponse = ProcessImgurLink(imgurLink, imgUrl);
                if (IsValidGifUrl(imgurResponse.Url) && !forceLoadGifAsImage)
                    return LoadGif(imgUrl, imgurLink);





                info = imgurResponse.Info;
                imageUrl = imgurResponse.Url;
                downloadUrl = forceLoadGifAsImage ? imgurResponse.Url : imgurResponse.ThumbnailUrl;
                if (imgurResponse.CachedImage != null)
                {
                    int imgCount = 1;

                    if (info.data.images != null && info.data.images.Count > 0)
                    {
                        int imgIndex = 0;
                        if (imgurResponse.CachedImage.ImgNum > 0)
                            imgIndex = imgurResponse.CachedImage.ImgNum - 1;
                        ImgurImage img = info.data.images[imgIndex];
                        imageUrl = img.link;
                        imgCount = info.data.images_count;
                    }

                    ImagePreview.ImageText = imgurResponse.CachedImage.Title;
                    ImagePreview.ImgNumber = _albumImageIndex + 1;
                    ImagePreview.ImgCount = imgCount;

                    if (imgurResponse.CachedImage.Source != null)
                    {
                        App.Current.Dispatcher.Invoke(() =>
                        {
                            ImagePreview.ImgSource = CreateImageSource(imgurResponse.CachedImage.Source);// StaticMethods.ByteArrayToBitmap(imgurResponse.CachedImage.Source);
                        });
                    }

                }
                else if (info != null)
                {
                    ImagePreview.ImgNumber = 1;
                    //    ImagePreview.Title = ImgurManager.GetImageTitle(info.data);
                    ImagePreview.ImgCount = info.data.images_count;
                }
            }

            if (ImagePreview.ImgSource == null)
            {
                _isDownloadingImage = true;
                if (_wc != null)
                {
                    _wc.CancelAsync();
                    _wc = null;
                    Logger.Debug("Canceling image preview async web request");
                }
                _wc = new TimeoutWebClient(6000);
                _wc.Proxy = null;

                _wc.DownloadProgressChanged += _wc_DownloadProgressChanged;
                _wc.DownloadDataCompleted += wc_DownloadDataCompleted;
                int imgCount = info != null ? info.data.images_count : 1;
                string albumUrl = info != null ? imgUrl : "";
                var downloadState = new ImageDownloadState(false, 1, imgCount, imageUrl, albumUrl);

                _wc.DownloadDataAsync(new Uri(downloadUrl), downloadState);
                Interlocked.Increment(ref _imgDownloadCount);
                showImage = false;
            }
            else
            {
                Messenger.Default.Send(new NotificationMessage<bool>(null, false, "ShowDownloadProgressWindow"));
            }
            return showImage;
        }


        private static void _wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            TimeoutWebClient wc = (TimeoutWebClient)sender;
            ImageDownloadState state = e.UserState as ImageDownloadState;
            long kilobytesToReceive = (e.TotalBytesToReceive / 1024);
            Messenger.Default.Send(new NotificationMessage<int>(null, e.ProgressPercentage, "SetDownloadProgress"));
        }

        public static void HideImagePopup()
        {
            if (ImagePreview != null)
            {
                ImagePreview.IsWindowVisible = false;
                ImagePreview.Close();
            }
            if (_isDownloadingImage)
            {
                _stopImagePopup = true;
                if (_wc != null)
                    _wc.CancelAsync();
                //if (_imgurInfo != null)
                //    _imgurInfo.CancelAsync();
            }
        }


        /// <summary>
        /// Processes an imgur link, and returns a download url and ImgurInfo object.
        /// </summary>
        /// <param name="imgurLink"></param>
        /// <param name="imgUrl"></param>
        /// <returns></returns>
        private static ProcessedImgurLink ProcessImgurLink(ImgurLink imgurLink, string imgUrl)
        {
            ObjectCache cache = MemoryCache.Default;
            CachedPreviewImage cachedImage = null;
            string id = imgurLink.ID;
            string downloadUrl = imgUrl;
            string thumnailUrl = ImgurManager.GetImgurLink(id, true);
            ImgurInfo info = null;
            if (imgurLink.LinkType == ImgurLinkType.Album || imgurLink.LinkType == ImgurLinkType.Gallery)
            {
                string albumUrl = "";
                if (imgurLink.LinkType == ImgurLinkType.Album)
                    albumUrl = string.Format("https://api.imgur.com/3/album/{0}", id);
                else if (imgurLink.LinkType == ImgurLinkType.Gallery)
                    albumUrl = string.Format("https://api.imgur.com/3/gallery/{0}", id);

                var cachedImgurInfo = cache.Get("ImgurInfo-" + imgUrl) as ImgurInfo;
                if (cachedImgurInfo == null)
                {
                    info = ImgurManager.DownloadImgurInfo(albumUrl);
                    if (info != null)
                        cache.Set("ImgurInfo-" + imgUrl, info, new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) });
                }
                else
                    info = cachedImgurInfo;

                if (info != null && info.success)
                {
                    _albumImageQueue.Clear();
                    if (info.data != null && info.data.images != null)
                    {
                        int n = 1;
                        foreach (var img in info.data.images)
                        {
                            CachedPreviewImage cachedImg = cache.Get(img.link) as CachedPreviewImage;
                            if (cachedImg != null && cachedImg.TotalImgCount == info.data.images_count)
                            {
                                _albumImageQueue[img.link] = cachedImg;
                            }
                            else
                            {
                                _albumImageQueue[img.link] = null;
                            }
                            n++;
                        }

                        var firstImage = info.data.images[_albumImageIndex];
                        if (firstImage != null)
                        {
                            downloadUrl = ImgurManager.GetImgurLink(firstImage.id, false, VirtualPathUtility.GetExtension(firstImage.link));
                            thumnailUrl = ImgurManager.GetImgurLink(firstImage.id, true, VirtualPathUtility.GetExtension(firstImage.link));
                            cachedImage = cache[downloadUrl] as CachedPreviewImage;

                            int startIndex = 1;
                            //if (cachedImage == null)
                            //    startIndex = 0;
                            StartImageQueueBackgroundDownload(info, startIndex);
                        }
                    }
                    else
                    {
                        downloadUrl = ImgurManager.GetImgurLink(info.data.id, false, VirtualPathUtility.GetExtension(info.data.link));
                        thumnailUrl = ImgurManager.GetImgurLink(info.data.id, true, VirtualPathUtility.GetExtension(info.data.link));
                        cachedImage = cache[downloadUrl] as CachedPreviewImage;
                    }
                }
            }
            else if (imgurLink.LinkType == ImgurLinkType.WrappedImage || imgurLink.LinkType == ImgurLinkType.Image)
            {
                string jsonUrl = string.Format("https://api.imgur.com/3/image/{0}", id);
                var cachedImgurInfo = cache.Get("ImgurInfo-"+imgUrl) as ImgurInfo;
                if (cachedImgurInfo == null)
                {
                    info = ImgurManager.DownloadImgurInfo(jsonUrl);
                    if (info != null)
                        cache.Set("ImgurInfo-" + imgUrl, info, new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) });
                }
                else
                    info = cachedImgurInfo;

                cachedImage = cache[downloadUrl] as CachedPreviewImage;
                if (info != null && info.success)
                {
                    string ext = VirtualPathUtility.GetExtension(info.data.link);
                    downloadUrl = ImgurManager.GetImgurLink(info.data.id, false, ext);
                    thumnailUrl = ImgurManager.GetImgurLink(info.data.id, true, ext);
                }
            }

            return new ProcessedImgurLink(thumnailUrl, downloadUrl, info, cachedImage);// new Tuple<string, ImgurInfo>(downloadUrl, info);
        }




        private static void DownloadImgurTitleAsync(string id, string imgUrl)
        {
            if (String.IsNullOrEmpty(id) || _isDownloadingImgurInfo) return;

            if (_imgurInfo != null)
            {
                _imgurInfo.CancelAsync();
                _imgurInfo = null;
                Logger.Debug("Canceling image preview async web request for imgur info");
            }
            _imgurInfo = new TimeoutWebClient(4000)
            {
                Proxy = null,
                Encoding = Encoding.UTF8,
            };
            _imgurInfo.Headers.Add("Authorization", string.Format("Client-ID {0}", ImgurManager.ImgurAppKey));
            _imgurInfo.DownloadStringCompleted += imgurInfo_DownloadDataCompleted;

            string jsonUrl = string.Format("https://api.imgur.com/3/image/{0}", id);
            _imgurInfo.DownloadStringAsync(new Uri("http://imgur.com/" + id), imgUrl);
            _isDownloadingImgurInfo = true;
            Logger.Debug("Downloading imgur title async: " + "http://imgur.com/" + id);
        }

        private static void imgurInfo_DownloadDataCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            ((TimeoutWebClient)sender).DownloadStringCompleted += imgurInfo_DownloadDataCompleted;
            _isDownloadingImgurInfo = false;
            if (!e.Cancelled && e.Error == null)
            {
                App.Current.Dispatcher.Invoke(new Action(() =>
                {
                    string title = Regex.Match(e.Result, @"<meta property=""og:title"" content=\""(?<Title>[\s\S]*?)""/>", RegexOptions.IgnoreCase).Groups["Title"].Value;
                    title = System.Net.WebUtility.HtmlDecode(title);

                    if (!String.IsNullOrEmpty(title))
                    {
                        title = title.Trim();

                        ObjectCache cache = MemoryCache.Default;
                        var cachedImage = cache.Get(e.UserState.ToString()) as CachedPreviewImage;
                        if (cachedImage != null)
                            cachedImage.Title = title;
                        else
                        {
                            cache.Set(e.UserState.ToString(), new CachedPreviewImage(null, new Size(0, 0), title, e.UserState.ToString()), new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) });
                        }
                        Logger.Debug("setting image preview title: " + title);

                        ImagePreview.ImageText = title;
                    }
                }));
            }
            _imgurInfo = null;
        }



        private static void StartImageQueueBackgroundDownload(ImgurInfo info, int startIndex)
        {
            if ((_imageQueueBackgroundDownloadThread != null && _imageQueueBackgroundDownloadThread.IsAlive) || info == null)
                return;

            _imageQueueBackgroundDownloadThread = new Thread(() =>
            {
                while (_isDownloadingImage)
                    Thread.Sleep(1);
                ObjectCache cache = MemoryCache.Default;
                int n = startIndex + 1;
                foreach (ImgurImage bgImg in info.data.images.Skip(startIndex))
                {

                    string imageUrl = bgImg.link;
                    string downloadUrl = ImgurManager.GetImgurLink(bgImg.id, true, VirtualPathUtility.GetExtension(bgImg.link));
                    var cachedImage = cache[imageUrl] as CachedPreviewImage;

                    while (_imgDownloadCount > 2)
                    {
                        // Set is never called, so we wait always until the timeout occurs
                        using (EventWaitHandle tmpEvent = new ManualResetEvent(false))
                        {
                            tmpEvent.WaitOne(TimeSpan.FromMilliseconds(10));
                        }
                    }
                    if (n - _albumImageIndex > 10)
                        break;
                    if (cachedImage != null)
                    {
                        cachedImage.TotalImgCount = info.data.images_count;
                        cachedImage.ImgNum = n;
                        _albumImageQueue[imageUrl] = cachedImage;
                    }
                    if (cachedImage == null)
                    {
                        bool success = false;

                        if (IsValidGifUrl(imageUrl))
                        {
                            if (ImgurManager.EnableImgurVideo)
                            {
                                string title = bgImg.title;
                                if (!String.IsNullOrEmpty(bgImg.description))
                                {
                                    if (!string.IsNullOrEmpty(title))
                                        title += "\r\n";
                                    title += bgImg.description;
                                }
                                var policy = new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) };
                                cachedImage = new CachedPreviewImage(null, new Size((double)bgImg.width, (double)bgImg.height), bgImg.title, imageUrl, ImgurManager.GetVideoUrlForImgurItem(bgImg), n, info.data.images_count);
                                cache.Set(imageUrl, cachedImage, policy);

                                if (_albumImageQueue.Any(x => x.Key.Equals(imageUrl, StringComparison.OrdinalIgnoreCase)))
                                {
                                    _albumImageQueue[imageUrl] = cachedImage;
                                }
                                success = true;
                            }
                            else if (GfyCatManager.EnableGfyCat)
                            {
                                GfyItem item = null;
                                try
                                {
                                    item = GfyCatManager.GetGfyItem(imageUrl);
                                }
                                catch (Exception ex)
                                {
                                    Logger.ErrorException("Error getting GfyItem", ex);
                                }
                                if (item != null)
                                {
                                    var policy = new CacheItemPolicy() { SlidingExpiration = new TimeSpan(0, 30, 0) };
                                    cachedImage = new CachedPreviewImage(null, new Size(item.width, item.height), item.title, imageUrl, GfyCatManager.GetVideoUrlForGfyItem(item), n, info.data.images_count);
                                    cache.Set(imageUrl, cachedImage, policy);

                                    if (_albumImageQueue.Any(x => x.Key.Equals(imageUrl, StringComparison.OrdinalIgnoreCase)))
                                    {
                                        _albumImageQueue[imageUrl] = cachedImage;
                                    }
                                    success = true;
                                }
                            }
                        }
                        if (IsValidImageUrl(imageUrl) && !success)
                        {
                            WebClient wc = new WebClient();
                            wc.DownloadDataCompleted += wc_DownloadDataCompleted;
                            var downloadState = new ImageDownloadState(true, n, info.data.images_count, imageUrl, info.data.link);
                            wc.DownloadDataAsync(new Uri(downloadUrl), downloadState);
                            Interlocked.Increment(ref _imgDownloadCount);
                            success = true;
                        }
                    }

                    n++;
                }
            }) { IsBackground = true };
            _imageQueueBackgroundDownloadThread.Start();
        }


        private static void wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
        {
            _isDownloadingImage = false;

            WebClient wc = (WebClient)sender;
            wc.DownloadDataCompleted -= wc_DownloadDataCompleted;
            var userData = e.UserState as ImageDownloadState;
            if (_ignoreDownloadFinished.ContainsKey(userData.ImageUrl) && _ignoreDownloadFinished[userData.ImageUrl] == true)
            {
                bool ignore;
                _ignoreDownloadFinished.TryRemove(userData.ImageUrl, out ignore);
                Logger.Debug("Ignoring datacompleted and returning");
                return;
            }


            Interlocked.Decrement(ref _imgDownloadCount);
            if (e.Cancelled || e.Error != null)
            {
                if (e.Error != null)
                {
                    var response = (HttpWebResponse)((WebException)e.Error).Response;
                    int statuscode = 0;
                    if (response != null)
                        statuscode = response.StatusCode.GetIntValue();
                    Messenger.Default.Send(new NotificationMessage<int>(null, 0, "SetDownloadProgress"));
                    Messenger.Default.Send(new NotificationMessage<int>(null, statuscode, "SetDownloadErrorCode"));
                }
                else
                {
                    Messenger.Default.Send(new NotificationMessage<bool>(null, false, "ShowDownloadProgressWindow"));
                    Messenger.Default.Send(new NotificationMessage<int>(null, 0, "SetDownloadProgress"));
                }

                _stopImagePopup = false;
                return;
            }
            Messenger.Default.Send(new NotificationMessage<bool>(null, false, "ShowDownloadProgressWindow"));
            Messenger.Default.Send(new NotificationMessage<int>(null, 0, "SetDownloadProgress"));

            BitmapFrame img = null;
            App.Current.Dispatcher.Invoke(() =>
            {
                img = CreateImageSource(e.Result);
            });

            string imageTitle = "";

            Logger.Debug(@"Downloaded image {0}\{1} at {2}", userData.ImageNumber, userData.ImageCount, userData.ImageUrl);
            if (img != null)
            {

                ObjectCache cache = MemoryCache.Default;
                CacheItemPolicy policy = new CacheItemPolicy();
                policy.SlidingExpiration = new TimeSpan(0, 30, 0);
                CachedPreviewImage cachedImg = cache.Get(userData.ImageUrl) as CachedPreviewImage;

                if (cachedImg != null)
                {
                    imageTitle = cachedImg.Title;
                }

                ImgurInfo info = ImgurManager.GetImgurInfoForUrl(userData.ImageUrl);
                if (info != null)
                {
                    string title = "";
                    if (userData.ImageNumber == 1)
                    {
                        title = ImgurManager.GetImageTitle(info.data);//info.data.title.ToString();
                    }
                    if (info.data.images != null)
                    {
                        ImgurImage i = info.data.images.FirstOrDefault(x => x.link == userData.ImageUrl);
                        if (!string.IsNullOrEmpty(title))
                            title += Environment.NewLine + Environment.NewLine;
                        title += ImgurManager.GetImageTitle(i);
                    }

                    if (!string.IsNullOrEmpty(title))
                        imageTitle = title.ToString();
                    //else
                    //    DownloadImgurTitleAsync(info.data.id, userData.ImageUrl);
                }
                
                var newCachedImage = new CachedPreviewImage(e.Result, new Size(img.Width, img.Height), imageTitle, userData.ImageUrl, string.Empty, userData.ImageNumber, userData.ImageCount);
                cache.Set(userData.ImageUrl, newCachedImage, policy);
                if (_albumImageQueue.Any(x => x.Key.Equals(userData.ImageUrl, StringComparison.OrdinalIgnoreCase)) && userData.ImageCount>0)
                {
                    _albumImageQueue[userData.ImageUrl] = newCachedImage;
                }
                if (img != null && !userData.LoadInBackground)
                {
                    if (_stopImagePopup)
                    {
                        Logger.Debug("stopping image preview");
                        return;
                    }

                    _linkUrl = !String.IsNullOrEmpty(userData.AlbumUrl) ? userData.AlbumUrl : userData.ImageUrl;
                    App.Current.Dispatcher.Invoke(() =>
                            {
                                ImagePreview.ImageText = imageTitle;

                                ImagePreview.ImgSource = img;

                                ImagePreview.IsWindowVisible = true;

                                Logger.Debug("opening image preview window");

                            });
                }
            }
            _stopImagePopup = false;
        }

        private static BitmapFrame CreateImageSource(byte[] bytes)
        {

            var stream = new MemoryStream(bytes);
            try
            {
                var frame = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
                return frame;
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error opening image", err);
                Mouse.SetCursor(Cursors.No);
            }
            finally
            {
                stream.Close();


            }
            return null;
        }




        private static void _hoverTimer_Tick(object sender, EventArgs e)
        {

            DispatcherTimer timer = sender as DispatcherTimer;
            TextEditor cc = timer.Tag as TextEditor;
            var mousePos = Mouse.GetPosition(cc);
            double verticalChange = mousePos.X - ImagePreviewMousePosition.X;
            bool linkChanged = false;
            var pos = cc.GetPositionFromPoint(mousePos);
            if (pos.HasValue)
            {
                ImageZoomVisualLineLinkText link = GetLinkFromPosition(cc, pos.Value);
                if (link != null)
                {
                    if (link.NavigateUri.AbsoluteUri == _lastHoveredLink)
                    {
                        return;
                    }
                    else
                        linkChanged = true;
                }
            }
            if (verticalChange > -5 && verticalChange < 5 && !linkChanged)
                return;

            HideImagePopup();
            Messenger.Default.Send(new NotificationMessage<bool>(null, false, "ShowDownloadProgressWindow"));


        }

        private static void TextEditor_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (_albumImageQueue.Count > 1 && ImagePreview != null && ImagePreview.IsLoaded)
            {
                e.Handled = true;
                ScrollAlbum(e.Delta);
            }
        }
        private static void TextEditor_MouseMove(object sender, MouseEventArgs e)
        {
            TextEditor cc = sender as TextEditor;
            if (ImagePreview == null)
                return;
            var pos = cc.GetPositionFromPoint(e.GetPosition(cc));
            if (pos.HasValue)
            {
                ImageZoomVisualLineLinkText link = GetLinkFromPosition(cc, pos.Value);
                if (link != null)
                {
                    ImagePreview.UpdatePosition();
                    Messenger.Default.Send(new NotificationMessage<Point>(null, cc.PointToScreen(e.GetPosition(cc)), "UpdateDownloadProgressPosition"));
                    return;
                }
            }
            HideImagePopup();
            Messenger.Default.Send(new NotificationMessage<bool>(null, false, "ShowDownloadProgressWindow"));
        }

        private static void TextEditor_MouseHover(object sender, MouseEventArgs e)
        {
            var element = sender as TextEditor;

            var mousePos = e.GetPosition(element);
            var pos = element.GetPositionFromPoint(mousePos);
            ImagePreviewMousePosition = mousePos;
            if (pos.HasValue)
            {
                ImageZoomVisualLineLinkText link = GetLinkFromPosition(element, pos.Value);
                if (link != null)
                {
                    try
                    {
                        if (ImagePreview != null && ImagePreview.IsLoaded)
                            return;
                        if (ImagePreview != null)
                        {
                            ImagePreview.Close();
                            ImagePreview = null;
                        }

                        if (DownloadProgress != null)
                        {
                            DownloadProgress.Close();
                            DownloadProgress = null;
                        }
                        //  GC.Collect();
                        ImagePreview = new ImagePreviewWindow();
                        ImagePreview.Owner = Window.GetWindow(element);
                        ImagePreview.Show();

                        Debug.WriteLine("Creating new image preview window");
                        var mousePoint = element.PointToScreen(e.GetPosition(element));
                        DownloadProgress = new DownloadProgressWindow(mousePoint.X, mousePoint.Y);
                        DownloadProgress.Owner = Window.GetWindow(element);
                        DownloadProgress.Show();
                        Debug.WriteLine("Creating new download progress window");
                        DownloadProgress.DoWhenLoaded(x =>
                            {
                                Messenger.Default.Send(new NotificationMessage<Point>(null, element.PointToScreen(e.GetPosition(element)), "UpdateDownloadProgressPosition"));
                            });
                        _lastHoveredLink = link.NavigateUri.AbsoluteUri;

                        ShowImage(link.NavigateUri);
                    }
                    finally
                    {
                        e.Handled = true;
                    }
                }
            }
        }




        private static ImageFormat GetImageFormat(byte[] bytes)
        {
            // see http://www.mikekunz.com/image_file_header.html  
            var bmp = Encoding.ASCII.GetBytes("BM");     // BMP
            var gif = Encoding.ASCII.GetBytes("GIF");    // GIF
            var png = new byte[] { 137, 80, 78, 71 };    // PNG
            var tiff = new byte[] { 73, 73, 42 };         // TIFF
            var tiff2 = new byte[] { 77, 77, 42 };         // TIFF
            var jpeg = new byte[] { 255, 216, 255, 224 }; // jpeg
            var jpeg2 = new byte[] { 255, 216, 255, 225 }; // jpeg canon

            if (bmp.SequenceEqual(bytes.Take(bmp.Length)))
                return ImageFormat.bmp;

            if (gif.SequenceEqual(bytes.Take(gif.Length)))
                return ImageFormat.gif;

            if (png.SequenceEqual(bytes.Take(png.Length)))
                return ImageFormat.png;

            if (tiff.SequenceEqual(bytes.Take(tiff.Length)))
                return ImageFormat.tiff;

            if (tiff2.SequenceEqual(bytes.Take(tiff2.Length)))
                return ImageFormat.tiff;

            if (jpeg.SequenceEqual(bytes.Take(jpeg.Length)))
                return ImageFormat.jpeg;

            if (jpeg2.SequenceEqual(bytes.Take(jpeg2.Length)))
                return ImageFormat.jpeg;

            return ImageFormat.unknown;
        }



        private static bool IsValidImageUrl(string link)
        {
            Regex imgRegex = new Regex(@"(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*\.(?:jpg|jpeg|png|gif|bmp))(?:\?([^#]*))?(?:#(.*))?", RegexOptions.IgnoreCase);

            Match imgMatch = imgRegex.Match(link);
            return imgMatch.Success;
        }
        private static bool IsValidGifUrl(string link)
        {
            Regex imgRegex = new Regex(@"(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*\.(?:gif))(?:\?([^#]*))?(?:#(.*))?", RegexOptions.IgnoreCase);
            Match imgMatch = imgRegex.Match(link);
            return imgMatch.Success;
        }
        private static bool IsValidVideoUrl(string link)
        {
            Regex imgRegex = new Regex(@"(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*\.(?:mp4|webm|avi))(?:\?([^#]*))?(?:#(.*))?", RegexOptions.IgnoreCase);
            Match imgMatch = imgRegex.Match(link);
            return imgMatch.Success;
        }


        private static ImageZoomVisualLineLinkText GetLinkFromPosition(TextEditor editor, TextViewPosition pos)
        {
            var hoveredVisualLine = editor.TextArea.TextView.GetVisualLine(pos.Line);
            foreach (var VisualLineElement in hoveredVisualLine.Elements.Where(x => x.GetType() == typeof(ImageZoomVisualLineLinkText)))
            {
                if (pos.VisualColumn >= VisualLineElement.VisualColumn && pos.VisualColumn < (VisualLineElement.VisualColumn + VisualLineElement.VisualLength))
                {

                    return (ImageZoomVisualLineLinkText)VisualLineElement;
                }
            }
            return null;
        }
    }
}